package com.cordys.opensource.crom;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import com.cordys.cpc.bsf.busobject.BSF;
import com.cordys.cpc.bsf.busobject.BusObject;
import com.cordys.cpc.bsf.busobject.BusObjectIterator;
import com.cordys.cpc.bsf.busobject.BusObjectUID;
import com.cordys.cpc.bsf.busobject.PersistentUID;
import com.cordys.cpc.bsf.busobject.QueryObject;
import com.cordys.cpc.bsf.busobject.exception.BsfApplicationRuntimeException;
import com.cordys.cpc.bsf.busobject.exception.BsfInvalidUIDException;
import com.cordys.cpc.bsf.busobject.exception.BsfRuntimeException;
import com.cordys.cpc.bsf.classinfo.AttributeInfo;
import com.cordys.cpc.bsf.classinfo.AttributeInfo.Persistence;
import com.cordys.cpc.bsf.classinfo.ClassInfo;
import com.cordys.cpc.bsf.classinfo.RelationInfo_Composite;
import com.cordys.cpc.bsf.classinfo.RelationInfo_FK;
import com.cordys.cpc.bsf.metadata.UnsignedInt;
import com.cordys.cpc.bsf.relation.MultiRelation;
import com.cordys.cpc.bsf.relation.SingleRelation;
import com.cordys.cpc.bsf.types.UnsignedByte;
import com.cordys.cpc.bsf.util.ObjectHelper;
import com.eibus.util.logger.CordysLogger;

/**
 * Holds the Class BusObjectHelper. This class contains utility methods around the hanlding of Bus objects.
 */
public class BusObjectHelper
{
    /** Holds the logger to use. */
    private static final CordysLogger LOG = CordysLogger.getCordysLogger(BusObjectHelper.class);
    /** Holds the class to query parameter type mapping. */
    private static HashMap<Class<?>, Integer> classToQueryParameterTypeMapping = new HashMap<Class<?>, Integer>();

    static
    {
        classToQueryParameterTypeMapping.put(int.class, QueryObject.PARAM_INT);
        classToQueryParameterTypeMapping.put(long.class, QueryObject.PARAM_DOUBLE);
        classToQueryParameterTypeMapping.put(BigDecimal.class, QueryObject.PARAM_BIGDECIMAL);
        classToQueryParameterTypeMapping.put(boolean.class, QueryObject.PARAM_BOOLEAN);
        classToQueryParameterTypeMapping.put(BusObject.class, QueryObject.PARAM_BUSOBJECT);
        classToQueryParameterTypeMapping.put(Date.class, QueryObject.PARAM_DATE);
        classToQueryParameterTypeMapping.put(double.class, QueryObject.PARAM_DOUBLE);
        classToQueryParameterTypeMapping.put(String.class, QueryObject.PARAM_STRING);
        // classToQueryParameterTypeMapping.put(int.class, QueryObject.PARAM_XML);
    }

    /**
     * Returns the type that is to be used in queries in which a parameter is used that maps to the field with the AttributeInfo.
     * 
     * @param attribute The attribute that can be found by looking in the classinfo that relates to the table of the query
     * @return The query parameter type.
     */
    public static int getQueryParameterType(AttributeInfo attribute)
    {
        int parameterType = classToQueryParameterTypeMapping.get(attribute.getAttributeClass());

        if (parameterType == 0)
        {
            parameterType = QueryObject.PARAM_STRING; // default is string.
        }
        return parameterType;
    }

    /**
     * Returns the composite child relations of the Class.
     * 
     * @param boClass The bo class
     * @return A HashMap with the relations stored by their related class. This means that it is easy to search for a Composite
     *         relation for a specified child class.
     */
    // Casting relatedClass always to BusObject
    @SuppressWarnings("unchecked")
    public static Map<Class<? extends BusObject>, RelationInfo_Composite> getChildRelations(Class<? extends BusObject> boClass)
    {
        HashMap<Class<? extends BusObject>, RelationInfo_Composite> relations = new HashMap<Class<? extends BusObject>, RelationInfo_Composite>();
        ClassInfo classInfo = BusObject._getClassInfo(boClass);

        if (classInfo == null)
        {
            return relations;
        }

        // navigate the relations
        Enumeration<?> allRelations = classInfo.getRelationInfos();

        while (allRelations.hasMoreElements())
        {
            Object o = allRelations.nextElement();

            if (!(o instanceof RelationInfo_Composite))
            {
                continue; // Not a relation we can work with.
            }

            RelationInfo_Composite relation = (RelationInfo_Composite) o;

            try
            {
                Class<? extends BusObject> relatedClass = relation.getRelatedClass();
                relations.put(relatedClass, relation);
            }
            catch (ClassNotFoundException cnfe)
            {
                // can't do anything with this relation, let's just ignore.
            }
        }
        return relations;
    }

    /**
     * Simple helper method that does the try/catch and also nicely casts to BusObject. These things are unfortunately not
     * implemented in foreignKeyRelation.getRelatedClass()
     * 
     * @param foreignKeyRelation The foreign key relation
     * @return the related class
     */
    public static Class<? extends BusObject> getRelatedClass(RelationInfo_FK foreignKeyRelation)
    {
        try
        {
            @SuppressWarnings("unchecked")
            Class<? extends BusObject> relatedClass = foreignKeyRelation.getRelatedClass();
            return relatedClass;
        }
        catch (ClassNotFoundException e)
        {
            throw new BsfRuntimeException(e);
        }
    }

    /**
     * Simple helper method that does the try/catch and also nicely casts to BusObject. These things are unfortunately not
     * implemented in compositeRelation.getRelatedClass()
     * 
     * @param compositeRelation The composite relation
     * @return the related class
     */
    public static Class<? extends BusObject> getRelatedClass(RelationInfo_Composite compositeRelation)
    {
        try
        {
            @SuppressWarnings("unchecked")
            Class<? extends BusObject> relatedClass = compositeRelation.getRelatedClass();
            return relatedClass;
        }
        catch (ClassNotFoundException e)
        {
            throw new BsfRuntimeException(e);
        }
    }

    /**
     * Returns the foreign key relation if it exists between the two classes.
     * 
     * @param from The from
     * @param to The to
     * @return The foreign key relation
     */
    public static RelationInfo_FK getForeignKeyRelation(Class<? extends BusObject> from, Class<? extends BusObject> to)
    {
        RelationInfo_FK relation = null;
        List<RelationInfo_FK> relations = getForeignKeyRelations(from).get(to);

        if ((relations != null) && (relations.size() > 0))
        {
            relation = relations.get(0);
        }
        return relation;
    }

    /**
     * Returns the foreign key relations of the Class.
     * 
     * @param boClass The bo class
     * @return A HashMap with the relations stored by their related class. This means that it is easy to search for a ForeignKey
     *         relation with a specified target.
     */
    @SuppressWarnings("unchecked")
    // Casting relatedClass always to BusObject
    public static HashMap<Class<? extends BusObject>, List<RelationInfo_FK>> getForeignKeyRelations(
            Class<? extends BusObject> boClass)
    {
        HashMap<Class<? extends BusObject>, List<RelationInfo_FK>> relations = new HashMap<Class<? extends BusObject>, List<RelationInfo_FK>>();

        if (boClass == null)
        {
            return relations;
        }

        ClassInfo classInfo = BusObject._getClassInfo(boClass);

        if (classInfo == null)
        {
            return relations;
        }

        // navigate the relations
        Enumeration<?> allRelations = classInfo.getRelationInfos();

        while (allRelations.hasMoreElements())
        {
            Object o = allRelations.nextElement();

            if (!(o instanceof RelationInfo_FK))
            {
                continue; // Not a relation we can work with.
            }

            RelationInfo_FK relation = (RelationInfo_FK) o;

            try
            {
                Class<? extends BusObject> relatedClass = relation.getRelatedClass();
                List<RelationInfo_FK> fkRelations = relations.get(relatedClass);

                if (fkRelations == null)
                {
                    fkRelations = new ArrayList<RelationInfo_FK>();
                    relations.put(relatedClass, fkRelations);
                }
                fkRelations.add(relation);
            }
            catch (ClassNotFoundException cnfe)
            {
                // can't do anything with this relation, let's just ignore.
            }
        }
        return relations;
    }

    /**
     * Determines whether there is a foreign key relation between the two busobjects. If there is, then depending on the direction
     * of the FK, the attributes of one object will be copied into the other (or the BusObject.setAttributeIntegrityValue() method
     * will be invoked if the object is newly inserted).
     * 
     * @param from The from
     * @param to The to
     * @return true if the relationship was found and filled. False otherwise.
     * @deprecated Use the method relateObjects with a foreign key relation.
     */
    @Deprecated
    public static boolean relateObjects(BusObject from, BusObject to)
    {
        // Now traverse the relationships at DB level and see if there is a foreign key.
        RelationInfo_FK foreignKeyRelation = BusObjectHelper.getForeignKeyRelation(from.getClass(), to.getClass());
        return relateObjects(from, to, foreignKeyRelation);
    }

    /**
     * Determines whether there is a foreign key relation between the two busobjects. If there is, then depending on the direction
     * of the FK, the attributes of one object will be copied into the other (or the BusObject.setAttributeIntegrityValue() method
     * will be invoked if the object is newly inserted).
     * 
     * @param from The from
     * @param to The to
     * @param foreignKeyRelation The foreign key relation
     * @return true if the relationship was found and filled. False otherwise.
     */
    public static boolean relateObjects(BusObject from, BusObject to, RelationInfo_FK foreignKeyRelation)
    {
        // Check if we have found an FK. If not, then we're done.
        if (foreignKeyRelation == null)
        {
            return false;
        }

        // Now get the attributes of the FK and use them to establish the relationship between the database objects.
        String[] attributesInTo = foreignKeyRelation.getRelatedAttributes(); // E.g., Note.TicketId
        String[] attributesInFrom = foreignKeyRelation.getLocalAttributes(); // E.g. Ticket.Id

        // Determine direction of foreign key.
        if (foreignKeyRelation.getLocalIsPK()) // The child has a foreign key to the parent (e.g., Calculation with
                                               // Position in EXAMPLE 1)
        {
            // Now check if the parent is new; If so, then just set the attribute integrity value.
            // Otherwise we need to copy the key fields from the parent into the child.
            if (from.isPersistentNew())
            {
                // Enforce a change in the from... This is sometimes necessary
                // March 28, 2012: Actually: it is causing issues in some places...
                // Uncommenting the code for now.
                // What we have noticed, is that when the to property is set,
                // it is entered as a NULL value in the database, instead of
                // it being filled with the new value of the from during the oninsert.
                // April 4, 2012: One more finding. If the 'to' is already existing in the
                // database, then only we have to fill in values. However, if the 'to'
                // is newly inserted, then we should NOT set the values of the FK,
                // but rely on WS-AppServer/XQY to take care.
                // Probably same logic has to be applied in case of FK in different direction

                // Also another thought here:
                // This code now is only tested with tables that have auto-generated
                // primary keys. But what if the table does not have an auto-generated
                // key, but a functional key. In that case, this functional key
                // is probably passed from the client. So, 'from' would still be
                // in state "PersistentNew", but we'd have to copy the values of
                // 'from' into 'to'. Will the method setAttributeValueIntegrity take
                // care of this? If not, then we'd have to combine the logic
                // in this if statement with the logic in the else part.

                if (to.isPersistentExisting())
                {
                    // If the to object is inserted as well,
                    // then we have to create the tag of the foreignkey field
                    // in the xml object, otherwise the call
                    // to setAttributeValueIntegrity will not have any effect
                    for (int i = 0; i < attributesInTo.length; i++)
                    {
                        to.setProperty(attributesInTo[i], "", 0);
                    }
                }
                to.setAttributeValueIntegrity(from, attributesInFrom, attributesInTo);
            }
            else
            {
                // No need to check whether the parent holds the primary key, since otherwise it cannot yet exist.
                for (int i = 0; i < attributesInTo.length; i++)
                {
                    String toAttributeName = attributesInTo[i];
                    String fromAttributeName = attributesInFrom[i];

                    if (!from.isNull(fromAttributeName))
                    {
                        String fromAttributeValue = from.getStringProperty(fromAttributeName);
                        // Copy the parent property into the child with the right name
                        to.setProperty(toAttributeName, fromAttributeValue, 0);
                    }
                }
            }
        }
        else
        // The parent has a foreign key to the child (e.g. AuditReport with Document in EXAMPLE 2)
        {
            if (to.isPersistentNew())
            {
                // Enforce a change in the from... This is sometimes necessary
                for (int i = 0; i < attributesInFrom.length; i++)
                {
                    from.setProperty(attributesInFrom[i], "", 0);
                }
                from.setAttributeValueIntegrity(to, attributesInTo, attributesInFrom);
            }
            else
            {
                // No need to check whether the parent holds the primary key, since otherwise it cannot yet exist.
                for (int i = 0; i < attributesInTo.length; i++)
                {
                    String toAttributeName = attributesInTo[i];
                    String fromAttributeName = attributesInFrom[i];

                    if (!to.isNull(toAttributeName))
                    {
                        String toAttributeValue = to.getStringProperty(toAttributeName);
                        // Copy the parent property into the child with the right name
                        from.setProperty(fromAttributeName, toAttributeValue, 0);
                    }
                }
            }
        }

        return true;
    }

    /**
     * Checks whether an object of the specified class with the specified keys is already in the transaction.
     * 
     * @param objectClass The object class
     * @param keys The keys
     * @return The object in current transaction
     */
    @SuppressWarnings("unchecked")
    public static <T extends BusObject> T getObjectInCurrentTransaction(Class<T> objectClass, Object... keys)
    {
        if ((keys.length % 2) != 0)
        {
            throw new IllegalArgumentException("Last key ('" + keys[keys.length - 1] + "') does not come with a value");
        }

        BusObjectIterator<?> objects = BSF.getObjectManager().getUpdatedObjects();

        while (objects.hasMoreElements())
        {
            BusObject object = objects.nextElement();

            if (objectClass.equals(object.getClass()))
            {
                boolean matches = true;

                for (int i = 0; i < keys.length; i += 2)
                {
                    String keyName = String.valueOf(keys[i]);
                    String keyValue = String.valueOf(keys[i + 1]);

                    if (!keyValue.equals(object.getStringProperty(keyName)))
                    {
                        matches = false;
                        break;
                    }
                }

                if (matches)
                {
                    // This warning is suppressed, since the classes are equal, so the cast will never fail.
                    return (T) object;
                }
            }
        }
        return null;
    }

    /**
     * Recursively goes through the object and removes all the empty children.
     * 
     * @param object The object
     * @return true if the object is empty
     */
    public static boolean removeEmptyChildren(BusObject object)
    {
        if (object == null)
        {
            return true;
        }

        ClassInfo classInfo = object._classInfo();
        Enumeration<?> relations = classInfo.getRelationInfos();
        boolean isEmpty = true;

        while (relations.hasMoreElements())
        {
            Object o = relations.nextElement();

            if (o instanceof RelationInfo_Composite)
            {
                RelationInfo_Composite relation = (RelationInfo_Composite) o;

                if (relation.isMultiOcc())
                {
                    MultiRelation childrenRelation = object._getMultiRelation(relation.getIdentifier(), true);
                    childrenRelation.load();

                    BusObjectIterator<?> children = childrenRelation.getObjects();

                    while (children.hasMoreElements())
                    {
                        BusObject child = children.nextElement();

                        if (isNullObject(child))
                        {
                            if (removeEmptyChildren(child))
                            {
                                childrenRelation.removeObject(child);
                            }
                            else
                            {
                                isEmpty = false;
                            }
                        }
                        else
                        {
                            removeEmptyChildren(child);
                            isEmpty = false;
                        }
                    }
                }
                else
                {
                    SingleRelation childRelation = object._getSingleRelation(relation.getIdentifier(), true);
                    childRelation.load();

                    BusObject child = childRelation.getLocalObject();

                    if (isNullObject(child))
                    {
                        if (removeEmptyChildren(child))
                        {
                            childRelation.setLocalObject(null);
                        }
                        else
                        {
                            isEmpty = false;
                        }
                    }
                    else
                    {
                        removeEmptyChildren(child);
                        isEmpty = false;
                    }
                }
            }
        }
        return isEmpty;
    }

    /**
     * Tests whether the object is null or whether all attributes inside the object are null.
     * 
     * @param object The object
     * @return true, if is null object
     */
    public static boolean isNullObject(BusObject object)
    {
        if (object == null)
        {
            return true;
        }

        AttributeInfo[] attributes = object._classInfo().getAttributeInfos2();

        for (AttributeInfo attributeInfo : attributes)
        {
            if (!object.isNull(attributeInfo.getName()))
            {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the UID for the object, or null if it cannot be created. The UID is the "semantic key" of the object. E.g., for
     * Ticket it will consist of the value of the Ticket.Id, for SolutionRevision it will consist of SolutionRevision.SolutionId
     * and SolutionRevision.Revision
     * 
     * @param object The object
     * @return The UID of the object, or null if it cannot be created
     */
    public static BusObjectUID getUID(BusObject object)
    {
        if (object == null)
        {
            return null;
        }

        try
        {
            return new PersistentUID(object, object._classInfo().getUIDElements());
        }
        catch (BsfInvalidUIDException e)
        {
            // cannot create PUID, so it is not present in old.
            return null;
        }
    }

    /**
     * Analyses the differences between the children of the domain object and it's original and if necessary
     * updates/inserts/deletes them.
     * 
     * @param busObject The domain object for which to analyse the children.
     * @param originalBusObject The object containing the old values of the children (from tuple/old/new protocol)
     */
    static void updateChildren(BusObject busObject, BusObject originalBusObject)
    {
        Enumeration<?> relations = busObject._classInfo().getRelationInfos();

        while (relations.hasMoreElements())
        {
            Object o = relations.nextElement();

            if (o instanceof RelationInfo_Composite)
            {
                RelationInfo_Composite relation = (RelationInfo_Composite) o;

                if (!relation.isMultiOcc())
                {
                    updateChildObject(busObject, originalBusObject, relation);
                }
            }
        }
        relations = busObject._classInfo().getRelationInfos();

        while (relations.hasMoreElements())
        {
            Object o = relations.nextElement();

            if (o instanceof RelationInfo_Composite)
            {
                RelationInfo_Composite relation = (RelationInfo_Composite) o;

                if (relation.isMultiOcc())
                {
                    updateChildren(busObject, originalBusObject, relation);
                }
            }
        }
    }

    /**
     * Insert children.
     * 
     * @param busObject The bus object
     */
    static void insertChildren(BusObject busObject)
    {
        BusObjectHelper.updateChildren(busObject, (BusObject) null);
    }

    /**
     * Delete children.
     * 
     * @param busObject The bus object
     */
    static void deleteChildren(BusObject busObject)
    {
        Enumeration<?> relations = busObject._classInfo().getRelationInfos();

        while (relations.hasMoreElements())
        {
            Object o = relations.nextElement();

            if (o instanceof RelationInfo_Composite)
            {
                RelationInfo_Composite relation = (RelationInfo_Composite) o;

                if (!relation.isMultiOcc())
                {
                    deleteChildObject(busObject, relation);
                }
            }
        }
        relations = busObject._classInfo().getRelationInfos();

        while (relations.hasMoreElements())
        {
            Object o = relations.nextElement();

            if (o instanceof RelationInfo_Composite)
            {
                RelationInfo_Composite relation = (RelationInfo_Composite) o;

                if (relation.isMultiOcc())
                {
                    deleteChildren(busObject, relation);
                }
            }
        }
    }

    /**
     * Delete child object.
     * 
     * @param busObject The bus object
     * @param relation The relation
     */
    private static void deleteChildObject(BusObject busObject, RelationInfo_Composite relation)
    {
        String relationID = relation.getIdentifier();

        SingleRelation relationNewChild = busObject._getSingleRelation(relationID, true);
        relationNewChild.load();

        BusObject newChild = relationNewChild.getLocalObject();

        if (newChild == null)
        {
            return;
        }

        // Check if we have UID attributes, otherwise we are not able to do an update on the child.
        ClassInfo ci = newChild._classInfo();
        String[] uidElements = ci.getUIDElements();

        if ((uidElements == null) || (uidElements.length == 0))
        {
            return;
        }

        // Workaround for a WS-AppServer bug; if an object goes through the Update method, then the
        // WS-AppServer will create empty objects for the children, and then this framework will try
        // to update the child, whereas it should be inserted.
        if ((newChild != null) && isNullObject(newChild))
        {
            newChild = null;
        }

        // Remove child from it's parent, so that we can invoke the delete method.
        relationNewChild.setLocalObject(null);
        // Set the domain parent for the old child, so that in application code we
        // still have the context available.
        setDomainParent(busObject, newChild, relation);
        newChild.setAsPersistent();
        newChild.delete();
    }

    /**
     * Delete children.
     * 
     * @param busObject The bus object
     * @param relation The relation
     */
    private static void deleteChildren(BusObject busObject, RelationInfo_Composite relation)
    {
        String relationID = relation.getIdentifier();
        // Load the new children
        MultiRelation relationNewChildren = busObject._getMultiRelation(relationID, true);
        relationNewChildren.load();

        @SuppressWarnings("unchecked")
        // This is required for BOP4.1
        BusObjectIterator<BusObject> newChildren = relationNewChildren.getObjects();

        while (newChildren.hasMoreElements())
        {
            BusObject child = newChildren.nextElement();
            relationNewChildren.removeObject(child);
            setDomainParent(busObject, child, relation);
            child.setAsPersistent();
            child.delete();
            relationNewChildren.addObject(child);
        }
    }

    /**
     * Navigates the relation with the specified ID on the domain object in both the new and old version of the object. If the
     * object is present in either one or both it will be inserted, updated or deleted.
     * 
     * @param busObject The object on which to navigate the relation.
     * @param originalBusObject The original bus object
     * @param relation The relation
     */
    private static void updateChildObject(BusObject busObject, BusObject originalBusObject, RelationInfo_Composite relation)
    {
        String relationID = relation.getIdentifier();

        SingleRelation relationNewChild = busObject._getSingleRelation(relationID, true);
        relationNewChild.load();

        BusObject newChild = relationNewChild.getLocalObject();

        SingleRelation relationExistingChild = null;
        BusObject oldChild = null;

        if (originalBusObject != null) // check whether we are in update or insert
        {
            relationExistingChild = originalBusObject._getSingleRelation(relationID, true);
            relationExistingChild.load();
            oldChild = relationExistingChild.getLocalObject();
        }

        if ((newChild == null) && (oldChild == null))
        {
            return;
        }

        // Check if we have UID attributes, otherwise we are not able to do an update on the child.
        ClassInfo ci = (newChild != null) ? newChild._classInfo() : oldChild._classInfo();
        String[] uidElements = ci.getUIDElements();

        if ((uidElements == null) || (uidElements.length == 0))
        {
            return;
        }

        // Workaround for a WS-AppServer bug; if an object goes through the Update method, then the
        // WS-AppServer will create empty objects for the children, and then this framework will try
        // to update the child, whereas it should be inserted.
        if ((newChild != null) && isNullObject(newChild))
        {
            newChild = null;
        }

        // Workaround for bug in our own code
        // When we do a query to read full Ticket, it may have objects with only null attributes inside.
        // This effectively means the object is not present, so in that case we will remove it.
        if ((oldChild != null) && isNullObject(oldChild))
        {
            oldChild = null;
        }

        if (newChild == null) // It is either delete or no action.
        {
            if (oldChild != null) // child only present in old, so it must be deleted
            {
                // Remove child from it's parent, so that we can invoke the delete method.
                relationExistingChild.setLocalObject(null);
                // Set the domain parent for the old child, so that in application code we
                // still have the context available.
                setDomainParent(originalBusObject, oldChild, relation);
                oldChild.setAsPersistent();
                oldChild.delete();
            }
            else
            // child not present in old and new, no need to do anything.
            {
            }
        }
        else
        // it is either insert or update.
        {
            if (oldChild == null) // child only present in new, so it must be inserted
            {
                // Remove the child from it's parent so that we can invoke the insert method.
                relationNewChild.setLocalObject(null);
                // Set the domain parent for the child, so that in application code we
                // still have the context available.
                setDomainParent(busObject, newChild, relation);
                // Insert the newChild.
                newChild.insert();
                // Restore newChild in it's parent
                relationNewChild.setLocalObject(newChild);
            }
            else
            // child present in old and in new, so it must be updated.
            {
                if (BusObjectHelper.objectsAreEqual(oldChild, newChild))
                {
                    // Also set the domain parent in case the object has not changed.
                    // The reason for this is that the metadata must be filled.
                    setDomainParent(busObject, newChild, relation);
                    // Objects are not changed, but perhaps their children are changed.
                    updateChildren(newChild, oldChild);
                }
                else
                {
                    // Remove the child from it's parent so that we can invoke the update method.
                    relationNewChild.setLocalObject(null);
                    // Set the domain parent for the child, so that in application code we
                    // still have the context available.
                    setDomainParent(busObject, newChild, relation);

                    updateChild(newChild, oldChild);

                    // Restore the updated child in it's parent
                    relationNewChild.setLocalObject(newChild);
                }
            }
        }
    }

    /**
     * Util method to set the domain parent for the child if both implement the DomainObject interface. This can be replaced later
     * with a generic administration, but for current refactoring this is the quickest step.
     * 
     * @param parent The parent
     * @param child The child.
     * @param relation The relation
     */
    private static void setDomainParent(BusObject parent, BusObject child, RelationInfo_Composite relation)
    {
        BusObjectWrapper.setDomainParent(parent, child, relation.getName());
    }

    /**
     * Returns the child within the original object that has the same primary key as the child. E.g., for two positions, then
     * original domain object will be the "old" calculation, child will be the position found in the "new" calculation, relationID
     * will be "AGGR:CalculationPosition". If the calculationposition has id 1324, then it will search for a position with same id
     * inside the old calculation.
     * 
     * @param child The child that holds the primary key with which has to be searched within the old domain object.
     * @param oldChildren The old children
     * @return The old version of child, or null if it was not found.
     */
    private static BusObject getOldObject(BusObject child, BusObjectIterator<BusObject> oldChildren)
    {
        BusObjectUID childUID = BusObjectHelper.getUID(child);

        if (childUID == null) // no UID for the child object, means we cannot look it up in the original either.
        {
            return null;
        }

        // Load the existing children
        while (oldChildren.hasMoreElements())
        {
            BusObject oldChild = oldChildren.nextElement();
            BusObjectUID oldChildUID = BusObjectHelper.getUID(oldChild);

            if (childUID.equals(oldChildUID))
            {
                return oldChild;
            }
        }
        return null;
    }

    /**
     * Navigates the specified relationship on a bus object and returns the objects at the other end (typically an aggregated
     * child, like from Calculation return it's Positions).
     * 
     * @param busObject The bus object
     * @param originalBusObject The original bus object
     * @param relation The relation
     */
    @SuppressWarnings("unchecked")
    // This is required for BOP4.1
    private static void updateChildren(BusObject busObject, BusObject originalBusObject, RelationInfo_Composite relation)
    {
        String relationID = relation.getIdentifier();
        // Load the new children
        MultiRelation relationNewChildren = busObject._getMultiRelation(relationID, true);
        relationNewChildren.load();

        BusObjectIterator<BusObject> newChildren = relationNewChildren.getObjects();

        // TODO: we should check for whether the child objects have UID attributes.
        // If not, then we cannot do updates on them, everything will be regarded as inserts

        if (originalBusObject == null)
        {
            while (newChildren.hasMoreElements())
            {
                BusObject child = newChildren.nextElement();
                relationNewChildren.removeObject(child);
                setDomainParent(busObject, child, relation);
                child.insert();
                relationNewChildren.addObject(child);
            }
        }
        else
        {
            // Now we will analyse the new and old children for inserts, updates and deletes.
            // First we will analyse updates, then inserts, then deletes and then unchanged objects.
            // While going through this, the objects are removed from their parent, and then they are
            // added again to the parent.
            // However, we need to make sure we preserve the position of the
            // object in the parent. Therefore we first create a Vector that knows the position of all the children.
            Vector<BusObject> newChildrenInOrderedList = new Vector<BusObject>();

            while (newChildren.hasMoreElements())
            {
                newChildrenInOrderedList.add(newChildren.nextElement());
            }
            newChildren = relationNewChildren.getObjects();

            // Load the existing children
            MultiRelation relationExistingChildren = originalBusObject._getMultiRelation(relationID, true);
            relationExistingChildren.load();

            BusObjectIterator<BusObject> oldChildren = relationExistingChildren.getObjects();

            // Have WS-AppServer do the change analysis.
            BusObjectIterator<BusObject>[] changeAnalysis = ObjectHelper.analyseInnerObjects(newChildren, oldChildren);

            // Now iterate the updated children. The BusObject.update() method will be invoked on each updated child.
            // This can not be done on the object if it is still part of it's parent ("Nested object"). Therefore we
            // unlink it from the parent. Also, the BusObject.update() method requires both an old and a new. Therefore
            // we take the old, and update that with the attributes inside the new object, and then call the update
            // method on the old object. Afterwards, we need to set the old object back into the parent, since that now
            // has the right values.
            BusObjectIterator<BusObject> updated = changeAnalysis[ObjectHelper.CHANGED];

            while (updated.hasMoreElements())
            {
                BusObject newChild = updated.nextElement();

                // Find the old child
                BusObject oldChild = getOldObject(newChild, oldChildren);
                // And also reset the iterator for subsequent usage
                oldChildren = relationExistingChildren.getObjects();

                // Remove the object from it's parent so that we can invoke the update method on it;
                // This is also required because in the end all the children have to be put back in
                // the parent in the right order as they came in.
                relationNewChildren.removeObject(newChild);
                // ... and preserve the link with the parent so that the application logic can ask for it
                setDomainParent(busObject, newChild, relation);

                // Double check with our own compare function whether the object has
                // really changed. ObjectHelper.analyseInnerObjects uses ObjectHelper.objectsAreEqual
                // internally, but this function is too rigid because it only compares strings, and does
                // not take the type into account.
                // If case INC6834 is solved, we can remove this code.
                if (BusObjectHelper.objectsAreEqual(newChild, oldChild))
                {
                    updateChildren(newChild, oldChild);
                }
                else
                {
                    // Invoke the right helper function
                    updateChild(newChild, oldChild);
                }
            }

            BusObjectIterator<BusObject> inserted = changeAnalysis[ObjectHelper.INSERTED];

            while (inserted.hasMoreElements())
            {
                BusObject child = inserted.nextElement();
                relationNewChildren.removeObject(child);
                setDomainParent(busObject, child, relation);
                child.insert();
            }

            BusObjectIterator<BusObject> deleted = changeAnalysis[ObjectHelper.DELETED];

            while (deleted.hasMoreElements())
            {
                BusObject child = deleted.nextElement();
                // child.sys_getOuterObject()
                child._makeOuterObject();
                relationExistingChildren.removeObject(child);
                relationExistingChildren.removeObject(child);
                setDomainParent(busObject, child, relation);
                child.setAsPersistent();
                child.delete();
                // Restore the child in it's parent. Notice: this collection may now be out of order
                relationExistingChildren.addObject(child);
            }

            // Iterate through the unchanged children, and let the framework
            // analyse the grandchildren.
            BusObjectIterator<BusObject> unchanged = changeAnalysis[ObjectHelper.UNCHANGED];

            while (unchanged.hasMoreElements())
            {
                BusObject child = unchanged.nextElement();
                relationNewChildren.removeObject(child);
                setDomainParent(busObject, child, relation);

                // Find the old child
                BusObject oldChild = getOldObject(child, oldChildren);
                // And also reset the iterator for subsequent usage
                oldChildren = relationExistingChildren.getObjects();
                updateChildren(child, oldChild);
            }

            // Now put back all the children in the parent (in the proper order).
            for (BusObject object : newChildrenInOrderedList)
            {
                relationNewChildren.addObject(object);
            }
        }
    }

    /**
     * Update child.
     * 
     * @param newChild The new child
     * @param oldChild The old child
     */
    private static void updateChild(BusObject newChild, BusObject oldChild)
    {
        // Make a copy of the newChild so that we have the attributes.
        // Then clean the attributes of newChild, copy the attributes of oldChild into it,
        // set it to persistent, and then copy the attributes from the clone back into newChild.
        // This will construct a proper tuple/old/new for the child.
        BusObject newChildCopy = (BusObject) newChild.clone();

        // Clean up the attributes of newChild (to have a clean and pure copy from the old child)
        removeAttributes(newChild);

        // Remove the children and track them
        HashMap<RelationInfo_Composite, ArrayList<BusObject>> newChildrenPointers = unlinkChildren(newChild);

        // Copy oldChild into newChild.
        mapDBClassAttributes(newChild, oldChild, false);

        // Now remove the children of old child and add them to newChild, so that
        // WS-AppServer has the proper original XML data
        HashMap<RelationInfo_Composite, ArrayList<BusObject>> oldChildrenPointers = unlinkChildren(oldChild);
        addChildren(newChild, oldChildrenPointers); // this to actuate the old xml data

        // Start tracking changes
        newChild.setAsPersistent();
        // Copy the clone back into newChild (this will force the creation of the original XML data)
        mapDBClassAttributes(newChild, newChildCopy, false, false);

        // Again remove old children, since they do not belong in the newChild.
        oldChildrenPointers = unlinkChildren(newChild);

        // Now restore the original children in newChild
        addChildren(newChild, newChildrenPointers);

        // Now invoke the update
        newChild.update();

        // And restore the old children in the right location (probably not needed)
        oldChildrenPointers = unlinkChildren(newChild.getOriginalObject());
        addChildren(oldChild, oldChildrenPointers);
    }

    /**
     * This method adds the children.
     * 
     * @param parent The parent
     * @param children The children
     */
    private static void addChildren(BusObject parent, HashMap<RelationInfo_Composite, ArrayList<BusObject>> children)
    {
        for (RelationInfo_Composite relation : children.keySet())
        {
            ArrayList<BusObject> objects = children.get(relation);

            if (relation.isMultiOcc())
            {
                MultiRelation instanceRelation = parent._getMultiRelation(relation.getIdentifier(), true);
                if(null==instanceRelation)
                {
                	continue;
                }
                instanceRelation.load();

                for (BusObject object : objects)
                {
                    instanceRelation.addObject(object);
                }
            }
            else
            {
                SingleRelation instanceRelation = parent._getSingleRelation(relation.getIdentifier(), true);
                if(null==instanceRelation)
                {
                	continue;
                }
                instanceRelation.load();

                for (BusObject object : objects) // although this is a foreach, in practice there will always be one
                                                 // object only
                {
                    instanceRelation.setLocalObject(object);
                }
            }
        }
    }

    /**
     * Unlink children.
     * 
     * @param parent The parent
     * @return The hash map
     */
    private static HashMap<RelationInfo_Composite, ArrayList<BusObject>> unlinkChildren(BusObject parent)
    {
        HashMap<RelationInfo_Composite, ArrayList<BusObject>> unlinkedChildren = new HashMap<RelationInfo_Composite, ArrayList<BusObject>>();
        Enumeration<?> relations = parent._classInfo().getRelationInfos();

        while (relations.hasMoreElements())
        {
            Object o = relations.nextElement();

            if (o instanceof RelationInfo_Composite)
            {
                RelationInfo_Composite relation = (RelationInfo_Composite) o;
                ArrayList<BusObject> objects = new ArrayList<BusObject>();
                unlinkedChildren.put(relation, objects);

                if (relation.isMultiOcc())
                {
                    MultiRelation instanceRelation = parent._getMultiRelation(relation.getIdentifier(), true);
                    instanceRelation.load();

                    BusObjectIterator<?> relationChildren = instanceRelation.getObjects();

                    while (relationChildren.hasMoreElements())
                    {
                        BusObject child = relationChildren.nextElement();
                        instanceRelation.removeObject(child);
                        objects.add(child);
                    }
                }
                else
                {
                    SingleRelation instanceRelation = parent._getSingleRelation(relation.getIdentifier(), true);
                    instanceRelation.load();

                    BusObject child = instanceRelation.getLocalObject();

                    if (child != null)
                    {
                        objects.add(child);
                    }
                    instanceRelation.setLocalObject(null);
                }
            }
        }

        return unlinkedChildren;
    }

    /**
     * Makes it a null object.
     * 
     * @param object The object
     */
    private static void removeAttributes(BusObject object)
    {
        AttributeInfo[] attributes = object._classInfo().getAttributeInfos2();

        for (int i = 0; i < attributes.length; i++)
        {
            object.sys_dropPropertyXML(attributes[i].getName(), true);
        }
    }

    /**
     * This method compares the 2 objects. It returns whether or not they are equal to each other.
     * 
     * @param object1 The first object to use for the comparison.
     * @param object2 The second object to use for the comparison.
     * @return true if the obejcts are equal. Otherwise false.
     */
    public static boolean objectsAreEqual(BusObject object1, BusObject object2)
    {
        return objectsAreEqual(object1, object2, false);
    }

    /**
     * This method compares the 2 objects. It returns whether or not they are equal to each other.
     * 
     * @param object1 The first object to use for the comparison.
     * @param object2 The second object to use for the comparison.
     * @param ignoreTransient Whether or not transient attributes should be ignored while comparing. When set to true it means
     *            that transient attributes are not checked.
     * @return true if the obejcts are equal. Otherwise false.
     */
    public static boolean objectsAreEqual(BusObject object1, BusObject object2, boolean ignoreTransient)
    {
        // THIS CODE IS COPIED FROM WS-APPSERVER internals (ObjectHelper.objectsAreEqual).
        // This method can be removed when case INC6834 is solved. Invocations to this method
        // can then be replaced with ObjectHelper.objectsAreEqual

        // are objects the same?
        if (object1 == object2)
        {
            return true;
        }

        // do they have same class, else always false
        if (object1.getClass() != object2.getClass())
        {
            return false;
        }

        // Use non-published methods for now... (SDK should give more support...)
        AttributeInfo[] newObjectAttributes = object2._classInfo().getAttributeInfos2();

        for (int attr = 0; attr < newObjectAttributes.length; attr++)
        {
            AttributeInfo ai = newObjectAttributes[attr];

            if ((ignoreTransient == true) && (ai.getPersistence() == Persistence.TRANSIENT))
            {
                continue;
            }

            String attributeName = ai.getName();

            // Compare the string representations of the object
            String sValue1 = object1.getStringProperty(attributeName);
            String sValue2 = object2.getStringProperty(attributeName);

            // If both values-as-string are null, then don't compare...
            if ((sValue1 == null) && (sValue2 == null))
            {
                continue;
            }

            // If one but not both sides is null, then false;
            if ((sValue1 == null) || (sValue2 == null))
            {
                return false;
            }

            // Compare string values
            if (!sValue1.equals(sValue2))
            {
                if (ai.getAttributeClass().equals(double.class))
                {
                    // Compare the string representations of the object
                    double dValue1 = object1.getDoubleProperty(attributeName);
                    double dValue2 = object2.getDoubleProperty(attributeName);

                    if (dValue1 == dValue2)
                    {
                        continue;
                    }
                }
                return false;
            }
        }
        return true;
    }

    /**
     * This method will compare the 2 given objects. This is done based on the class info of the source object. This means that
     * the target object does not need to be of the same type. It just needs to have the same attributes that the source class
     * has.
     * <p>
     * After comparison it will return the attributes that have been changed. If the returned list is empty it means the objects
     * are equal to eachother.
     * </p>
     * 
     * @param source The source object for comparison.
     * @param target The target object to compare the source to.
     * @return The list of attributes that have changed. When the list is empty it means the objects are equal to eachother.
     */
    public static List<String> compareObjects(BusObject source, BusObject target)
    {
        return compareObjects(source, target, false);
    }

    /**
     * This method will compare the 2 given objects. This is done based on the class info of the source object. This means that
     * the target object does not need to be of the same type. It just needs to have the same attributes that the source class
     * has.
     * <p>
     * After comparison it will return the attributes that have been changed. If the returned list is empty it means the objects
     * are equal to eachother.
     * </p>
     * 
     * @param source The source object for comparison.
     * @param target The target object to compare the source to.
     * @param ignoreKeyFields Whether or not to ignore the primary key fields in the comparison. Default value is false.
     * @return The list of attributes that have changed. When the list is empty it means the objects are equal to each other.
     */
    public static List<String> compareObjects(BusObject source, BusObject target, boolean ignoreKeyFields)
    {
        return compareObjects(source, target, ignoreKeyFields, null, null);
    }

    /**
     * This method will compare the 2 given objects. This is done based on the class info of the source object. This means that
     * the target object does not need to be of the same type. It just needs to have the same attributes that the source class
     * has.
     * <p>
     * After comparison it will return the attributes that have been changed. If the returned list is empty it means the objects
     * are equal to eachother.
     * </p>
     * 
     * @param source The source object for comparison.
     * @param target The target object to compare the source to.
     * @param ignoreKeyFields Whether or not to ignore the primary key fields in the comparison. Default value is false.
     * @return The list of attributes that have changed. When the list is empty it means the objects are equal to each other.
     */
    public static List<String> compareObjects(BusObject source, BusObject target, boolean ignoreKeyFields,
            String[] includeFields, String[] excludeFields)
    {
        List<String> retVal = new ArrayList<String>();

        // Will hold the fields that should be ignored for comparison.
        List<String> ignored = new ArrayList<String>();
        List<String> included = new ArrayList<String>();

        ClassInfo ci = BusObject._getClassInfo(source.getClass());

        // Add the key fields to the ignore list.
        if (ignoreKeyFields == true)
        {
            String[] uidElements = ci.getUIDElements();
            for (String uidField : uidElements)
            {
                ignored.add(uidField);
            }

        }

        // Add the excluded fields to the ignore list.
        if (excludeFields != null && excludeFields.length > 0)
        {
            for (String field : excludeFields)
            {
                ignored.add(field);
            }
        }

        // Add the included fields if specified.
        if (includeFields != null && includeFields.length > 0)
        {
            for (String field : includeFields)
            {
                included.add(field);
            }
        }

        // Do the comparison
        AttributeInfo[] attributes = ci.getAttributeInfos2();

        for (AttributeInfo ai : attributes)
        {
            String attributeName = ai.getName();

            // Only do the comparison if the keyfields should not be ignored OR if the attribute name is not present in the ignore
            // list.
            if (ignoreKeyFields == false || !ignored.contains(attributeName)
                    && (included.size() == 0 || included.contains(attributeName)))
            {
                if (!compareAttributeValue(source, target, ai, false))
                {
                    retVal.add(attributeName);
                }
            }
        }

        return retVal;
    }

    /**
     * This method compares the value of the given attribute for both objects. If the value for the attribute is the same in both
     * objects then true is returned.
     * 
     * @param source The source object (basically the UI object)
     * @param dbObject The current DB object.
     * @param ai The attribute definition to check.
     * @param throwException When set to true this method will throw a tuple changed by other user exception.
     * @return true if the value for the attribute is the same in both objects. otherwise false.
     */
    public static boolean compareAttributeValue(BusObject source, BusObject dbObject, AttributeInfo ai, boolean throwException)
    {
        return compareAttributeValue(source, dbObject, ai.getName(), ai.getAttributeClass(), throwException);
    }

    /**
     * This method compares the value of the given attribute for both objects. If the value for the attribute is the same in both
     * objects then true is returned.
     * 
     * @param source The source object (basically the UI object)
     * @param dbObject The current DB object.
     * @param attribute The name of the attribute (ai.getName()).
     * @param typeClass The class of which the value of the attribute is (ai.getAttributeClass()).
     * @param throwException When set to true this method will throw a tuple changed by other user exception.
     * @return true if the value for the attribute is the same in both objects. otherwise false.
     */
    public static boolean compareAttributeValue(BusObject source, BusObject dbObject, String attribute, Class<?> typeClass,
            boolean throwException)
    {
        boolean returnValue = true;

        // There are several options:
        // 1. Both values are null (values are equal)
        // 2. 1 value is null, the other isn't. (values are not equal)
        // 3. Both have a value (values could be equal, but need to be compared)
        boolean sourceNull = source.isNull(attribute);
        boolean dbObjectNull = dbObject.isNull(attribute);

        if (!sourceNull == dbObjectNull)
        {
            returnValue = false;

            if (LOG.isDebugEnabled())
            {
                LOG.debug("Source or dbObject is null. Source: " + sourceNull + ", dbObject: " + dbObjectNull);
            }

            if (throwException)
            {
                throw new BsfApplicationRuntimeException("Tuple changed by other user on attribute " + attribute
                        + ". New value null: " + sourceNull + ". DB version null: " + dbObjectNull);
            }
        }

        // Only continue if the returnValue is still true. In other words: both have the same 'nil' status. So now
        // either both values are null, or both are filled. If they are filled we need to compare them.
        if ((returnValue == true) && !sourceNull)
        {
            Object value1 = null;
            Object value2 = null;

            if (typeClass.equals(String.class))
            {
                value1 = dbObject.getStringProperty(attribute);
                value2 = source.getStringProperty(attribute);
            }
            else if (typeClass.equals(long.class))
            {
                value1 = dbObject.getLongProperty(attribute);
                value2 = source.getLongProperty(attribute);
            }
            else if (typeClass.equals(int.class))
            {
                value1 = dbObject.getIntProperty(attribute);
                value2 = source.getIntProperty(attribute);
            }
            else if (typeClass.equals(boolean.class))
            {
                value1 = dbObject.getBooleanProperty(attribute);
                value2 = source.getBooleanProperty(attribute);
            }
            else if (typeClass.equals(short.class))
            {
                value1 = dbObject.getShortProperty(attribute);
                value2 = source.getShortProperty(attribute);
            }
            else if (typeClass.equals(double.class))
            {
                value1 = dbObject.getDoubleProperty(attribute);
                value2 = source.getDoubleProperty(attribute);
            }
            else if (typeClass.equals(BigDecimal.class))
            {
                value1 = dbObject.getBigDecimalProperty(attribute);
                value2 = source.getBigDecimalProperty(attribute);
            }
            else if (typeClass.equals(BigInteger.class))
            {
                value1 = dbObject.getBigIntegerProperty(attribute);
                value2 = source.getBigIntegerProperty(attribute);
            }
            else if (typeClass.equals(byte.class))
            {
                value1 = dbObject.getByteProperty(attribute);
                value2 = source.getByteProperty(attribute);
            }
            else if (typeClass.equals(Date.class))
            {
                value1 = dbObject.getDateProperty(attribute);
                value2 = source.getDateProperty(attribute);
            }
            else if (typeClass.equals(Timestamp.class))
            {
                value1 = dbObject.getDateTimestampProperty(attribute);
                value2 = source.getDateTimestampProperty(attribute);
            }
            else if (typeClass.equals(float.class))
            {
                value1 = dbObject.getFloatProperty(attribute);
                value2 = source.getFloatProperty(attribute);
            }
            else if (typeClass.equals(UnsignedByte.class))
            {
                value1 = dbObject.getUi1Property(attribute);
                value2 = source.getUi1Property(attribute);
            }
            else if (typeClass.equals(UnsignedInt.class))
            {
                value1 = dbObject.getUI1Property(attribute);
                value2 = source.getUI1Property(attribute);
            }

            // Break out when not the same.
            returnValue = value1.equals(value2);

            if (returnValue == false)
            {
                if (LOG.isDebugEnabled())
                {
                    LOG.debug("Field that is different: " + attribute + ". Current object: " + value1 + ", in the DB: " + value2);
                }

                if (throwException)
                {
                    throw new BsfApplicationRuntimeException("Tuple changed by other user on attribute " + attribute
                            + ". New value: " + value1 + ". DB value: " + value2);
                }
            }
        }

        return returnValue;
    }

    /**
     * This method checks if the value of the bit field is set to the desired value. This means the value is not NULL and the
     * boolean is actually equal to the desired boolean.
     * 
     * @param object The bus object to check.
     * @param attributeName The name of the attribute.
     * @param desiredValue The desired value to check.
     * @return true if the bus object's value for the attribute is not null AND equal to the desired value.
     */
    public static boolean checkBitField(BusObject object, String attributeName, boolean desiredValue)
    {
        boolean returnValue = false;

        if ((object != null) && !object.isNull(attributeName))
        {
            // The attribute is set. Now check if it has the desired value.
            if (desiredValue == object.getBooleanProperty(attributeName))
            {
                returnValue = true;
            }
        }

        return returnValue;
    }

    /**
     * This method checks if the value of the bit field is set to false. This means the value is not NULL and the boolean is
     * actually false.
     * 
     * @param object The bus object to check.
     * @param attributeName The name of the attribute.
     * @return true if the bus object's value for the attribute is not null AND equal to false.
     */
    public static boolean checkBitFieldFalse(BusObject object, String attributeName)
    {
        return checkBitField(object, attributeName, false);
    }

    /**
     * This method checks if the value of the bit field is set to true. This means the value is not NULL and the boolean is
     * actually true.
     * 
     * @param object The bus object to check.
     * @param attributeName The name of the attribute.
     * @return true if the bus object's value for the attribute is not null AND equal to true.
     */
    public static boolean checkBitFieldTrue(BusObject object, String attributeName)
    {
        return checkBitField(object, attributeName, true);
    }

    /**
     * This method will explicitly set all fields to null to force WsAppServer to create tags for these fields with nil="true"
     * 
     * @param busObject The bus object to fill.
     */
    public static void initializeObject(BusObject busObject)
    {
        try
        {
            ClassInfo ci = BusObject._getClassInfo(busObject.getClass());

            AttributeInfo[] attributes = ci.getAttributeInfos2();

            for (AttributeInfo ai : attributes)
            {
                if (busObject.isNull(ai.getName()))
                {
                    // Force the value.
                    busObject.setNull(ai.getName());
                }
            }
        }
        catch (Exception e)
        {
            // Ignore it, it'll never happen.
        }
    }

    /**
     * This method will compare the old and new value for the given attribute.
     * 
     * @param oldObject The old object.
     * @param newObject The new object.
     * @param attribute The attribute for which the old and new values should be compared.
     * @return true if the old and new value are different. Otherwise false.
     */
    public static boolean attributeValueChanged(BusObject oldObject, BusObject newObject, String attribute)
    {
        boolean returnValue = true;

        // First check the null-ability.
        if (oldObject.isNull(attribute) != newObject.isNull(attribute))
        {
            returnValue = false;
        }

        // Now check the values.
        if (returnValue
                && (!oldObject.isNull(attribute) && !oldObject.getStringProperty(attribute).equals(
                        newObject.getStringProperty(attribute))))
        {
            returnValue = false;
        }

        // returnValue is true if the value is the same. But if the value is the same it means the attribute value has
        // NOT changed, so false should be returned.
        return !returnValue;
    }

    /**
     * This method will copy the value from the source object to the destination object. This method is needed because WsApps
     * generates native types instead of class objects. It will throw an exception if the destination type is not the same as the
     * source type.
     * 
     * @param source The source bus object.
     * @param attrSource The name of the source attribute.
     * @param destination The destination object.
     * @param attrDestination The name of the destination attribute.
     */
    public static void copyAttributeValue(BusObject source, String attrSource, BusObject destination, String attrDestination)
    {
        copyAttributeValue(source, attrSource, destination, attrDestination, 0);
    }

    /**
     * This method will copy the value from the source object to the destination object. This method is needed because WsApps
     * generates native types instead of class objects. It will throw an exception if the destination type is not the same as the
     * source type.
     * 
     * @param source The source bus object.
     * @param attrSource The name of the source attribute.
     * @param destination The destination object.
     * @param attrDestination The name of the destination attribute.
     * @param setterMode The WS-ApPServer settermode to use. The default should be 0.
     */
    public static void copyAttributeValue(BusObject source, String attrSource, BusObject destination, String attrDestination,
            int setterMode)
    {
        try
        {
            if (source.isNull(attrSource))
            {
                // In Case of NULL
                destination.setNull(attrDestination, setterMode);
            }
            else
            {
                ClassInfo ci = BusObject._getClassInfo(source.getClass());
                AttributeInfo ai = ci.getAttributeInfo(attrSource);
                Class<?> sourceType = ai.getAttributeClass();

                ci = BusObject._getClassInfo(destination.getClass());
                ai = ci.getAttributeInfo(attrDestination);

                Class<?> destinationType = ai.getAttributeClass();

                // Destination type must be the same as source type.
                if (destinationType != sourceType)
                {
                    throw new BsfRuntimeException("Destination type (" + destinationType.toString()
                            + ") is not the same as source type (" + sourceType.toString(), null);
                }

                // Got through all types and find the right one, on top the most wanted.
                if (sourceType == String.class)
                {
                    destination.setProperty(attrDestination, source.getStringProperty(attrSource), setterMode);
                }
                else if (sourceType == boolean.class)
                {
                    destination.setProperty(attrDestination, source.getBooleanProperty(attrSource), setterMode);
                }
                else if (sourceType == int.class)
                {
                    destination.setProperty(attrDestination, source.getIntProperty(attrSource), setterMode);
                }
                else if (sourceType == long.class)
                {
                    destination.setProperty(attrDestination, source.getLongProperty(attrSource), setterMode);
                }
                else if (sourceType == Date.class)
                {
                    destination.setProperty(attrDestination, source.getDateProperty(attrSource), setterMode);
                }
                else if (sourceType == float.class)
                {
                    destination.setProperty(attrDestination, source.getFloatProperty(attrSource), setterMode);
                }
                else if (sourceType == byte.class)
                {
                    destination.setProperty(attrDestination, source.getByteProperty(attrSource), setterMode);
                }
                else if (sourceType == double.class)
                {
                    destination.setProperty(attrDestination, source.getDoubleProperty(attrSource), setterMode);
                }
                else if (sourceType == short.class)
                {
                    destination.setProperty(attrDestination, source.getShortProperty(attrSource), setterMode);
                }
                else if (sourceType == BigInteger.class)
                {
                    destination.setProperty(attrDestination, source.getBigIntegerProperty(attrSource), setterMode);
                }
                else if (sourceType == BigDecimal.class)
                {
                    destination.setProperty(attrDestination, source.getBigDecimalProperty(attrSource), setterMode);
                }
                else if (sourceType == Boolean.class)
                {
                    destination.setProperty(attrDestination, source.getBooleanProperty(attrSource), setterMode);
                }
                else if (sourceType == Byte.class)
                {
                    destination.setProperty(attrDestination, source.getByteProperty(attrSource), setterMode);
                }
                else if (sourceType == Double.class)
                {
                    destination.setProperty(attrDestination, source.getDoubleProperty(attrSource), setterMode);
                }
                else if (sourceType == Float.class)
                {
                    destination.setProperty(attrDestination, source.getFloatProperty(attrSource), setterMode);
                }
                else if (sourceType == Integer.class)
                {
                    destination.setProperty(attrDestination, source.getIntProperty(attrSource), setterMode);
                }
                else if (sourceType == Long.class)
                {
                    destination.setProperty(attrDestination, source.getLongProperty(attrSource), setterMode);
                }
                else if (sourceType == Short.class)
                {
                    destination.setProperty(attrDestination, source.getShortProperty(attrSource), setterMode);
                }
                else
                {
                    throw new BsfRuntimeException("Unknown type: " + destinationType.toString(), null);
                }
            }
        }
        catch (Exception e)
        {
            // Whenever an error occurs during the copy of the attribute.
            throw new BsfRuntimeException("Cannot copy Attribute value. " + e.getMessage(), e);
        }
    }

    /**
     * Maps all the attributes in the ClassInfo of the database class, hence no mapping array is required. All the attributes from
     * the database class will be copied from the database object to the domain object (or the other way, dependending on the
     * value of the fromDBToDomainObject).
     * 
     * @param dbObject The db object to copy to/from; This will also be used to retrieve the classinfo from which the list of
     *            attributes will be retrieved.
     * @param domainObject The domain object to copy from/to.
     * @param fromDBToDomainObject The direction of copying. If true, then the values of the db object will be copied into the
     *            domain object.
     */
    public static void mapDBClassAttributes(BusObject dbObject, BusObject domainObject, boolean fromDBToDomainObject)
    {
        mapDBClassAttributes(dbObject, domainObject, fromDBToDomainObject, true);
    }

    /**
     * Maps all the attributes in the ClassInfo of the database class, hence no mapping array is required. All the attributes from
     * the database class will be copied from the database object to the domain object (or the other way, dependending on the
     * value of the fromDBToDomainObject).
     * 
     * @param dbObject The db object to copy to/from; This will also be used to retrieve the classinfo from which the list of
     *            attributes will be retrieved.
     * @param domainObject The domain object to copy from/to.
     * @param fromDBToDomainObject The direction of copying. If true, then the values of the db object will be copied into the
     *            domain object.
     * @param ignoreNullAttributes Indicates whether null attributes must be copied or not.
     */
    public static void mapDBClassAttributes(BusObject dbObject, BusObject domainObject, boolean fromDBToDomainObject,
            boolean ignoreNullAttributes)
    {
        ClassInfo dbClassInfo = BusObject._getClassInfo(dbObject.getClass());
        ClassInfo domainClassInfo = BusObject._getClassInfo(domainObject.getClass());
        BusObject from = fromDBToDomainObject ? dbObject : domainObject;
        BusObject to = fromDBToDomainObject ? domainObject : dbObject;
        AttributeInfo[] attributes = dbClassInfo.getAttributeInfos2();

        for (AttributeInfo dbAttribute : attributes)
        {
            String attributeName = dbAttribute.getName();

            // Check whether the attribute exists in the domain layer.
            // If not, then sometimes we have seen that a composite child exists in the
            // domain object. E.g., ClaimCaseRevision.Client is a db text field, but in the domain model
            // ClaimCase aggregates the Client class. Therefore in such cases, the whole XML text under
            // the domain child will be put inside the DB attribute, which is not good.
            // On the other hand, many a times, database attributes are returned into domain objects
            // because of the querying mechanism. And in some places the framework code accidentally
            // depends on these attributes. Therefore we do an explicit check for this, although it is ugly.
            AttributeInfo domainAttribute = domainClassInfo.getAttributeInfo(attributeName);

            if (domainAttribute == null) // Could not find a matching attribute in the domain object.
            {
                // Do the explicit check to see if there is an aggregated child in the domain object
                // If so, then skip copying this attribute value.
                if (domainClassInfo.getRelationInfoByName(attributeName) != null)
                {
                    continue;
                }

                // Also check if ignoreNullAttributes is false. If null attributes
                // should not be ignored, then they will be copied from/to the domain object.
                // However, if the domain object does not contain this type of attribute,
                // then "!ignoreNullAttributes" will ALWAYS give a NULL value,
                // but then it should NOT be set
                if (!ignoreNullAttributes)
                {
                    continue;
                }
            }

            // There is a bug/feature in WS-AppServer; if a domain object is of type boolean, it will accept
            // both "true" / "false" and also "0" / "1". However, mapping "0" / "1" to db object will
            // result in a database exception for bit fields in SQL server. Hence we check whether it has
            // value of "0" / "1" and manually convert that to "true" / "false".
            if (dbAttribute.getAttributeClass().getName().equals("boolean") && !fromDBToDomainObject)
            {
                String value = domainObject.getStringProperty(attributeName);

                if ((value == null) || value.equals(""))
                {
                    continue; // Do not set it if the value is not given in the domain object.
                }

                String convertedValue = (value.equals("0") || value.toLowerCase().equals("false")) ? "false" : "true"; // It is
                                                                                                                       // only
                                                                                                                       // false if
                                                                                                                       // it is
                                                                                                                       // "0"
                                                                                                                       // or
                                                                                                                       // "false"
                dbObject.setProperty(attributeName, convertedValue, BusObject.IGNORE_CONSTRAINTS + BusObject.IGNORE_EVENTS);
            }
            else
            {
                mapAttribute(from, to, attributeName, ignoreNullAttributes);
            }
        }
    }

    /**
     * Map attribute.
     * 
     * @param sourceObject The source object
     * @param targetObject The target object
     * @param attributeName The attribute name
     * @param ignoreNullAttributes The ignore null attributes
     */
    private static void mapAttribute(BusObject sourceObject, BusObject targetObject, String attributeName,
            boolean ignoreNullAttributes)
    {
        int mode = BusObject.IGNORE_CONSTRAINTS + BusObject.IGNORE_EVENTS;

        if ((sourceObject == null) || sourceObject.isNull(attributeName))
        {
            // forward mapping
            if (!targetObject.isNull(attributeName) && !ignoreNullAttributes)
            {
                targetObject.setNull(attributeName, mode);
            }
        }
        else
        {
            String sourceValue = sourceObject.getStringProperty(attributeName);

            // if (sourceValue.equals("")) // This used to be "NULL" as well in previous versions of WS-AppServer
            // {
            // System.out.println("breakpoint");
            // return;
            // }
            if (targetObject.isNull(attributeName) || !sourceValue.equals(targetObject.getStringProperty(attributeName)))
            {
                targetObject.setProperty(attributeName, sourceValue, mode);
            }
        }
    }
    
    private static boolean containsAttribute(AttributeInfo[] destinationAttributes, AttributeInfo attribute)
    {
        for (AttributeInfo attributeInfo2 : destinationAttributes)
        {
            if (attributeInfo2.getName().equals(attribute.getName()))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Copy a BusObject from another BusObject. Delete all relation keys
     * @param source Source BusObject
     * @param destination Destination BusObject
     * @param deepcopy copy children
     * @param excludeList list of classes that would be ignored while copying. 
     * Works only with first level child classes of Source object
     */
    public static void doCopy(BusObject source, BusObject destination, boolean deepcopy,
            List<Class<? extends BusObject>> excludeList)
    {
        if (null==source)
        {
            return;
        }

        AttributeInfo[] sourceAttributes = source._classInfo().getAttributeInfos2();
        AttributeInfo[] destinationAttributes = destination._classInfo().getAttributeInfos2();
        for (AttributeInfo attributeInfo : sourceAttributes)
        {
            if (!source.isNull(attributeInfo.getName()))
            {
                // Copy those attributes that are both in source and destination
                if (containsAttribute(destinationAttributes, attributeInfo))
                {
                    copyAttributeValue(source, attributeInfo.getName(), destination, attributeInfo.getName());
                }
            }
        }

        /// Check all relations. If relation exists the add them to destination object
        Enumeration<?> relations = source._classInfo().getRelationInfos();
        //Holds all relation and objects to add 
        HashMap<RelationInfo_Composite, ArrayList<BusObject>> childrenPointers = new HashMap<RelationInfo_Composite, ArrayList<BusObject>>();

        while (relations.hasMoreElements() && deepcopy)
        {
            Object o = relations.nextElement();
            if (o instanceof RelationInfo_Composite)
            {
                RelationInfo_Composite relation = (RelationInfo_Composite) o;

                if (relation.isMultiOcc())
                {
                    MultiRelation instanceRelation = source._getMultiRelation(relation.getIdentifier(), true);
                    instanceRelation.load();
                    BusObjectIterator<?> relationChildren = instanceRelation.getObjects();

                    ArrayList<BusObject> childrenObj = new ArrayList<BusObject>();
                    while (relationChildren.hasMoreElements())
                    {
                        BusObject child = relationChildren.nextElement();
                        if(null!=child && (null==excludeList || !excludeList.contains(child.getClass())))
                        {
                            BusObject newChild = (BusObject) child.clone();
                            childrenObj.add(newChild);
                        }
                    }
                    childrenPointers.put(relation, childrenObj);
                }
                else
                {
                    SingleRelation instanceRelation = source._getSingleRelation(relation.getIdentifier(), true);
                    instanceRelation.load();

                    BusObject child = instanceRelation.getLocalObject();

                    ArrayList<BusObject> childrenObj = new ArrayList<BusObject>();
                    if(null!=child && (null==excludeList || !excludeList.contains(child.getClass())))
                    {
                        BusObject newchild = (BusObject) child.clone();
                        childrenObj.add(newchild);
                    }
                    childrenPointers.put(relation, childrenObj);
                }
            }
        }
        // Now add all copied children to the destination.
        addChildren(destination, childrenPointers);
        
        //The destination object is populated but the relationID still contains the reference to source object Ids. So remove them recursively
        removeKeyFields(destination, BusObjectMetadata.getMetadata(destination.getClass()));
    }

    /**
     * Walk recusively through the object tree and remove all relation keys
     * @param domainObject
     */
    private static void removeKeyFields(BusObject domainObject, BusObjectMetadata metadata)
    {
    	if (null==domainObject)
    	{
    		return;
    	}
    	
        if (BusObjectMetadata.isMasterDataObject(domainObject.getClass()))
        {
            // MasterData objects can (must) come with database key fields. So we should not remove them.
            //  This goes also for all children of the masterdata object (if any, which is not likely)
            removeMasterDataChildren(domainObject);
            return;
        }

    	// Remove primary key fields
    	removePrimaryKey(domainObject);
    	
    	// Now remove the relationship with the parent
        BusObject domainParent = BusObjectWrapper.getDomainParent(domainObject);
    	if (domainParent!=null)
    	{
        	RelationInfo_FK fkWithParent = metadata.getParentForeignKey();
        	String[] childAttributes = fkWithParent.getLocalAttributes();
        	for (String attribute : childAttributes)
            {
                domainObject.sys_dropPropertyXML(attribute, true);
            }
        	String[] parentAttributes = fkWithParent.getRelatedAttributes();
            for (String attribute : parentAttributes)
            {
                domainParent.sys_dropPropertyXML(attribute, true);
            }
        }

        Class<? extends BusObject> dbRevisionClass = metadata.getDbRevisionClass();
        if (dbRevisionClass!=null)
        {
            RelationInfo_FK revisionKey = BusObjectHelper.getForeignKeyRelation(metadata.getDbClass(), dbRevisionClass);
            String[] revisionAttributes = revisionKey.getRelatedAttributes();
            for (String attribute : revisionAttributes)
            {
                domainObject.sys_dropPropertyXML(attribute, true);
            }
        }
  
        // Iterate children and remove their key fields as well
        Enumeration<?> relations = domainObject._classInfo().getRelationInfos();
        while (relations.hasMoreElements())
        {
            Object o = relations.nextElement();
            if (!(o instanceof RelationInfo_Composite))
            {
                // Skip all non-composite relations. In practice that would be an ForeignKeyRelation, but they are probably
                //  present in DomainObjects
                continue;
            }
            RelationInfo_Composite relation = (RelationInfo_Composite) o;
            BusObjectMetadata childMetadata = metadata.getRelation(relation.getName());
            if (relation.isMultiOcc())
            {
                MultiRelation instanceRelation = domainObject._getMultiRelation(relation.getIdentifier(), true);
                instanceRelation.load();

                BusObjectIterator<?> relationChildren = instanceRelation.getObjects();
                while (relationChildren.hasMoreElements())
                {
                    BusObject child = relationChildren.nextElement();
                    removeKeyFields(child, childMetadata);
                }
            }
            else
            {
                SingleRelation instanceRelation = domainObject._getSingleRelation(relation.getIdentifier(), true);
                instanceRelation.load();

                BusObject child = instanceRelation.getLocalObject();
                removeKeyFields(child, childMetadata);
            }
        }
    }
    
    private static void removeMasterDataChildren(BusObject masterDataObject)
    {
        // Iterate children and remove their key fields as well
        Enumeration<?> relations = masterDataObject._classInfo().getRelationInfos();
        while (relations.hasMoreElements())
        {
            Object o = relations.nextElement();
            if (!(o instanceof RelationInfo_Composite))
            {
                // Skip all non-composite relations. In practice that would be an ForeignKeyRelation, but they are probably
                //  present in DomainObjects
                continue;
            }
            RelationInfo_Composite relation = (RelationInfo_Composite) o;
            if (relation.isMultiOcc())
            {
                MultiRelation instanceRelation = masterDataObject._getMultiRelation(relation.getIdentifier(), true);
                instanceRelation.load();

                BusObjectIterator<?> relationChildren = instanceRelation.getObjects();
                while (relationChildren.hasMoreElements())
                {
                    BusObject child = relationChildren.nextElement();
                    instanceRelation.removeObject(child);
                }
            }
            else
            {
                SingleRelation instanceRelation = masterDataObject._getSingleRelation(relation.getIdentifier(), true);
                instanceRelation.load();

                BusObject child = instanceRelation.getLocalObject();
                if (child!=null)
                {
                    instanceRelation.setLocalObject(null);
                }
            }
        }        
    }

    /**
     * Remove uid elements from object
     * @param domainObject
     */
    private static void removePrimaryKey(BusObject domainObject)
    {
        ClassInfo ci = BusObject._getClassInfo(domainObject.getClass());
        String[] uidElements = ci.getUIDElements();
        for (String attributeName : uidElements)
        {
            domainObject.sys_dropPropertyXML(attributeName, true);
        }
    }
}
